


import java.util.ArrayList;


/**
 * Class representing layer of nodes of a given type. This class also provide functions for applying steps to the layer
 * like computing the value or the error. Utility functions are also provided for easy layer linking  
 *
 */
public class Layer {

	private static final int NB_THREAD = 4;

	public ArrayList<AbstractNeuron> neuronList;

	public Layer(ArrayList<AbstractNeuron> list) {
		neuronList = list;
	}

	public void computeValue() {
		if (neuronList.size() <= NB_THREAD) {
			for (AbstractNeuron neuron : neuronList) {
				neuron.computeValue();
			}
		}
		else {
			ArrayList<ComputeValueThread> threads = new ArrayList<ComputeValueThread>();
			int nbElementThread = neuronList.size() / NB_THREAD;
			for (int i = 0; i < NB_THREAD; i++) {
				int start = i*nbElementThread;
				int end = ((i+1) * nbElementThread)-1;
				threads.add(new ComputeValueThread(neuronList.subList(start, end)));
			}
			for (ComputeValueThread thread : threads) {
				thread.start();
			}
			for (ComputeValueThread thread: threads) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	public void computeError() {
		if (neuronList.size() <= NB_THREAD) {
			for (AbstractNeuron neuron : neuronList) {
				neuron.computeError();
			}
		}
		else {
			ArrayList<ComputeErrorThread> threads = new ArrayList<ComputeErrorThread>();
			int nbElementThread = neuronList.size() / NB_THREAD;
			for (int i = 0; i < NB_THREAD; i++) {
				int start = i*nbElementThread;
				int end = ((i+1) * nbElementThread)-1;
				threads.add(new ComputeErrorThread(neuronList.subList(start, end)));
			}
			for (ComputeErrorThread thread : threads) {
				thread.start();
			}
			for (ComputeErrorThread thread: threads) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public void correctError() {
		if (neuronList.size() <= NB_THREAD) {
			for (AbstractNeuron neuron : neuronList) {
				neuron.correctError();
			}
		}
		else {
			ArrayList<CorrectErrorThread> threads = new ArrayList<CorrectErrorThread>();
			int nbElementThread = neuronList.size() / NB_THREAD;
			for (int i = 0; i < NB_THREAD; i++) {
				int start = i*nbElementThread;
				int end = ((i+1) * nbElementThread)-1;
				threads.add(new CorrectErrorThread(neuronList.subList(start, end)));
			}
			for (CorrectErrorThread thread : threads) {
				thread.start();
			}
			for (CorrectErrorThread thread: threads) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public void parallelLink(Layer l) {
		assert(neuronList.size() == l.neuronList.size());
		for (int i = 0; i < neuronList.size(); i++) {
			new NeuralLink(neuronList.get(i), l.neuronList.get(i));
		}
	}

	public void linkToOneNode(AbstractNeuron node) {
		assert(node != null);
		for (AbstractNeuron neuron : neuronList) {
			new NeuralLink(neuron, node);
		}
	}

	public void setInputValues(ArrayList<Integer> valueList) {
		assert(neuronList.size() == valueList.size());
		for (int i = 0; i < neuronList.size(); i ++) {
			((InputNeuron) neuronList.get(i)).setPixelValue(valueList.get(i));
		}
	}
}
